<!--
  代码编辑器
  属性：
    mode - 编辑模式
      类型：字符串
      可选值：js（默认），json，css，sql，html，text
    height - 编辑器高度
      类型：数字
      默认：160
    placeholder - 占位文本
      类型：字符串
      默认：''
    lineNumbers - 是否显示行号
      类型：布尔值
      默认：true
    readOnly - 是否只读
      类型：布尔值
      默认：false
  事件：
    on-blur - 编辑器失去焦点时触发，返回值：value
    on-submit-change - 点击提交修改时触发，返回值：value
-->
<template>
  <div class="code-editor-container">
    <codemirror ref="cmEditor" v-model="code" :options="cmOptions"></codemirror>
    <div class="toolbar-container">
      <Tooltip class="m-r-5px" v-if="mode == 'js' && readOnly == false"
               content="输入 pn_ 可以查看平台内置相关函数或变量" max-width="180" placement="top-start" transfer>
        <Icon type="md-information-circle" />
      </Tooltip>
      <a href="javascript:;" v-show="showSubmitChangeBtn" title="编辑之后请记得提交修改" @click.stop="submitChange" style="font-size: 12px;">提交修改</a>
      <div class="ace-toolbar">
        <a v-show="mode == 'json' && !readOnly" class="m-r-5px" title="格式化JSON" href="javascript:;"
           @click="formattingJson">
          <Icon type="md-list" :style="{color: '#FFF'}" size="14"/>
        </a>
        <a class="m-r-5px" title="复制" href="javascript:;" @click="copyContent">
          <Icon type="md-copy" :style="{color: '#FFF'}" size="13"/>
        </a>
        <a title="全屏模式代码编辑" href="javascript:;" @click="openFullscreenEditor">
          <Icon type="md-expand" :style="{color: '#FFF'}" size="13"/>
        </a>
      </div>
    </div>

    <div :id="fullscreenHeaderId" class="fullscreen-header-container">
      <span>全屏编辑<span v-if="readOnly">（只读）</span></span>
      <a href="javascript:;" title="按Esc键可退出全屏" class="float-right color-white" @click="closeFullscreenEditor">
        <Icon type="md-close" size="16"/>
      </a>
    </div>
    <div :id="fullscreenToolbarId" class="fullscreen-toolbar-container">
      <div class="float-left">
        <Tooltip class="m-r-5px" v-if="mode == 'js' && readOnly == false"
                 content="输入 pn_ 可以查看平台内置相关函数或变量" max-width="180" placement="top-start" transfer>
          <Icon type="md-information-circle" />
        </Tooltip>
        <a href="javascript:;" v-show="showSubmitChangeBtn" title="编辑之后请记得提交修改"
           @click.stop="submitChange">提交修改</a>
      </div>

      <div class="float-right">
        <a title="复制" href="javascript:;" @click="copyContent">
          <Icon type="md-copy" :style="{color: '#FFF'}" size="13"/>
        </a>
      </div>
    </div>

  </div>
</template>

<script>

import CodeMirror from 'codemirror'
import _ from "lodash";

const allMode = {
  'js': 'text/javascript',
  'json': 'application/json',
  'css': 'text/css',
  'sql': 'sql',
  'html': 'text/html',
  'text': 'text/plain'
}

export default {
  name: "CodeEditor",
  props: {
    value: String, // 值
    mode: { // 模式
      type: String,
      default: 'js'
    },
    height: { // 编辑器高度
      type: Number,
      default: 160
    },
    placeholder: {
      type: String,
      default: ''
    },
    lineNumbers: { // 是否显示行号
      type: Boolean,
      default: true
    },
    readOnly: { // 是否只读
      type: Boolean,
      default: false
    },
    showLint: { // 是否显示错误提示
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      isFullscreenEditor: false,
      fullscreenHeaderId: '',
      fullscreenToolbarId: '',

      originalCode: undefined,
      showSubmitChangeBtn: false,
      code: this.value,

      cmOptions: {
        autoCloseBrackets: true,
        autoRefresh: true,
        readOnly: this.readOnly,
        placeholder: this.placeholder,
        tabSize: 2,
        mode: this.mode ? allMode[this.mode] : allMode['js'],
        keyMap: "sublime",
        matchBrackets: true,
        showCursorWhenSelecting: true,
        theme: 'base16-dark',
        lineNumbers: this.lineNumbers,
        gutters: ["CodeMirror-lint-markers"],
        lint: this.readOnly || this.showLint == false ? false : {
          esversion: 6
        },
        extraKeys: { // 触发按键
          'Alt-/': 'autocomplete',
          'Esc': () => {
            this.closeFullscreenEditor()
          }
        },
        line: true,
        hintOptions: {
          hint: this.showHintHandler,
          completeSingle: false
        }
      },

      // widgets: [],
      // verifyCodeWaiting: null,
    }
  },
  mounted() {
    this._initialize()
  },
  beforeDestroy() {
    // 下面这段配置必须添加，主要解决代码编辑器的autoRefresh属性造成内存不释放的问题
    this.codemirror.setOption('autoRefresh', false)
  },
  methods: {
    // 初始化
    _initialize() {
      this.fullscreenHeaderId = 'fullscreenHeader_' + this.$PnUtil.uuid();
      this.fullscreenToolbarId = 'fullscreenToolbar_' + this.$PnUtil.uuid();

      this.codemirror.setSize('auto', this.height + 'px');

      this.codemirror.on('focus', (cm) => {
        if (!this.showSubmitChangeBtn) {
          this.originalCode = cm.getValue();
        }
      })

      // this.codemirror.on('change', () => {
      //   if (this.mode == 'js' || this.mode == 'json') {
      //     clearTimeout(this.verifyCodeWaiting)
      //     this.verifyCodeWaiting = setTimeout(this.verifyCode, 500)
      //   }
      // })

      this.codemirror.on('cursorActivity', (cm) => {
        if (this.originalCode != undefined && this.originalCode != cm.getValue()) {
          this.showSubmitChangeBtn = true;
        } else {
          this.showSubmitChangeBtn = false;
        }
      })

      this.codemirror.on('blur', (coder) => {
        if (!this.showSubmitChangeBtn) {
          this.originalCode = undefined
        }
        this.$emit('on-blur', coder.getValue())
      })

      this.codemirror.on('keypress', () => {
        if (!this.readOnly) {
          this.codemirror.closeHint();
          this.codemirror.showHint();
        }
      });

      // if (this.mode == 'js' || this.mode == 'json') {
      //   this.verifyCodeWaiting = setTimeout(this.verifyCode, 100)
      // }

    },
    /**
     * 校验JS语法（暂时弃用）
     **/
    /*verifyCode() {
      this.codemirror.operation(() => {
        for (let i = 0; i < this.widgets.length; ++i)
          this.codemirror.removeLineWidget(this.widgets[i]);
        this.widgets.length = 0;

        jshint.JSHINT(this.codemirror.getValue(), {
          "esversion": 6
        });
        for (let i = 0; i < jshint.JSHINT.errors.length; ++i) {
          let err = jshint.JSHINT.errors[i];
          if (!err) continue;
          let msg = document.createElement("div");
          let icon = msg.appendChild(document.createElement("span"));
          icon.innerHTML = "!! ";
          icon.className = "lint-error-icon";
          msg.appendChild(document.createTextNode(err.reason));
          msg.className = "lint-error";
          this.widgets.push(this.codemirror.addLineWidget(err.line - 1, msg, {coverGutter: false, noHScroll: true}));
        }
      });
      let info = this.codemirror.getScrollInfo();
      let after = this.codemirror.charCoords({line: this.codemirror.getCursor().line + 1, ch: 0}, "local").top;
      if (info.top + info.clientHeight < after)
        this.codemirror.scrollTo(null, after - info.clientHeight + 3);
    },*/

    showHintHandler() {
      let orig = null;
      switch (this.mode) {
        case 'json':
          orig = CodeMirror.hint.javascript
          break
        case 'html':
          orig = CodeMirror.hint.html
          break
        case 'css':
          orig = CodeMirror.hint.css
          break
        case 'sql':
          orig = CodeMirror.hint.sql
          break
        case 'text':
          orig = CodeMirror.hint.anyword
          break
        default:
          orig = CodeMirror.hint.javascript
      }
      let inner = orig(this.codemirror) || {from: this.codemirror.getCursor(), to: this.codemirror.getCursor(), list: []}
      const cursor = this.codemirror.getCursor()
      const token = this.codemirror.getTokenAt(cursor)
      this.currentHints.forEach(hint => {
        if (_.startsWith(hint.key, token.string) || _.startsWith('pn', token.string)) {
          inner.list.push({
            text: hint.text,
            displayText: hint.displayText
          });
        }
      })

      return inner;
    },

    /********************************************工具栏**********************************************/
    submitChange() {
      this.originalCode = this.codemirror.getValue();
      this.showSubmitChangeBtn = false;
      this.$emit('input', this.code);
      this.$emit('on-submit-change', this.code);
      this.$Message.success('编辑内容已生效');
      this.codemirror.focus()
    },
    formattingJson() {
      let cont = this.codemirror.getValue();
      this.codemirror.setValue(JSON.stringify(JSON.parse(cont), null, 2));
      // this.codemirror.focus();
    },
    copyContent() {
      let cont = this.codemirror.getValue();
      this.$PnUtil.copyToClipboard(cont);
      this.$Message.success('复制成功')
    },
    openFullscreenEditor() {
      this.isFullscreenEditor = true;

      let wrap = this.codemirror.getWrapperElement();
      wrap.style.height = 'calc(100% - 60px)';

      let $fullHeader = $('#' + this.fullscreenHeaderId);
      let $fullToolbar = $('#' + this.fullscreenToolbarId);
      let fs_p = $(wrap);
      if (this.codemirror._ic3Fullscreen == null) {
        this.codemirror._ic3Fullscreen = false;
        this.codemirror._ic3container = fs_p.parent();
      }

      if (!this.codemirror._ic3Fullscreen) {
        $fullHeader.show();
        $fullToolbar.show();
        $fullHeader.appendTo("body");
        $fullToolbar.appendTo("body");
        fs_p = fs_p.detach();
        fs_p.addClass("CodeMirror-fullscreen");
        fs_p.appendTo("body");
        // this.codemirror.focus();
        this.codemirror._ic3Fullscreen = true;
        this.codemirror.refresh()
      }
    },
    closeFullscreenEditor() {
      this.isFullscreenEditor = false;
      let wrap = this.codemirror.getWrapperElement();
      wrap.style.height = this.height + 'px';

      let $fullHeader = $('body #' + this.fullscreenHeaderId);
      let $fullToolbar = $('body #' + this.fullscreenToolbarId);
      $fullHeader.hide();
      $fullToolbar.hide();

      let fs_p = $(wrap);
      fs_p = fs_p.detach();
      fs_p.removeClass("CodeMirror-fullscreen");
      fs_p.appendTo(this.codemirror._ic3container);
      // this.codemirror.focus();
      this.codemirror._ic3Fullscreen = false;
      this.codemirror.refresh()
    },

  },
  computed: {
    codemirror() {
      return this.$refs.cmEditor.codemirror
    },
    currentHints() {
      let hints = [];
      if (this.mode == 'js' || this.mode == 'text') {
        hints = hints.concat(this.$PnCodeInsight.codeInsight)
      }
      return hints
    }
  },
  watch: {
    value(val) {
      this.code = val
    }
    // code (val) {
    //   console.log(val)
    //   this.$emit('input', val)
    // }
  }
}
</script>

<style lang="less" scoped>
.code-editor-container {
  border: 1px solid #424242;

  .toolbar-container {
    width: 100%;
    height: 25px;
    background-color: #2F3129;
    position: inherit;
    bottom: 0px;
    line-height: 25px;
    padding: 0px 5px 0px 5px;

    .ace-toolbar {
      float: right;
    }
  }
}

.fullscreen-header-container {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 30px;
  line-height: 30px;
  background-color: #303129;
  padding: 0px 10px 0px 10px;
  z-index: 9999;
  display: none;

  .ivu-icon {
    margin-top: -2px;
  }
}

.fullscreen-toolbar-container {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 30px;
  line-height: 30px;
  text-align: right;
  background-color: #303129;
  padding: 0px 10px 0px 10px;
  z-index: 9999;
  display: none;
}
</style>
